
package com.woniuxy.amusementparkos.util.lzw.src;

import com.woniuxy.amusementparkos.util.lzw.result.ResponseResult;
import weka.classifiers.Evaluation;
import weka.classifiers.trees.M5P;
import weka.core.Instances;
import weka.core.converters.CSVLoader;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.Remove;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 回归树算法，判断确定影响销售额的主要因素
 *      作用：财务和用户行为分析时，作为特殊工具
 *      基本流程：
 *         1.读取数据 - 2.数据预处理 - 3.模型学习 - 4.结果验证 - 5.有用数据提取
 *      目录结构：
 *         - util
 *           - lzw
 *             - data: *.csv (训练数据集)
 *             - lib: weka.jar (机器学习相关jar包)
 *             - src: RegressionUtil.java (回归算法工具类)
 *      回归算法通俗理解：
 *          以CSV文件内容为例，回归算法可以通过X1 X2 X3……（视为原因）&Y1 Y2（视为结果）等数据,
 *          生成为何可以得到Y1或Y2的函数，如果算法有效，将可以判断出哪些原因对结果产生的影响更大。
 *
 *          判断算法有效的方式，通常会使用一些验证算法对模型结果进行计算，它们会产生一些概率统计数据，
 *          可以协助人们判断其模型是否有效，本次算法中，我们主要关注相关系数（eval.correlationCoefficient()）。
 *
 * @author lizhiwen
 * @create 2020-04-13 7:20 下午
 */
public class RegressionUtil implements Serializable {

    /**
     * 读取数据
     *    以IO流形式读取数据
     * @return
     * @throws IOException
     */
    public static Instances getDate() throws IOException {
        /*
        读取数据：
           建议使用CSV的数据文件
         */
        CSVLoader loader = new CSVLoader();
        loader.setSource(new File("src/main/java/com/woniuxy/amusementparkos/util/lzw/data/ENB2012_data.csv"));
        Instances data = loader.getDataSet();
        return data;
    }

    /**
     * 数据预处理
     *     X 将只能对其中一个Y 进行计算，因此需要设定验证目标，并剔除无关目标
     * @param data
     * @return
     * @throws Exception
     */
    public static Instances preprocessData(Instances data) throws Exception {
        /*
        数据预处理
         */
        // set class index to Y1 (heating load)
        data.setClassIndex(data.numAttributes()-2);//point to Y1
        // remove last attribute Y2
        Remove remove = new Remove();
        remove.setOptions(new String[]{"-R",data.numAttributes()+""});
        remove.setInputFormat(data);
        data = Filter.useFilter(data, remove);
        return data;
    }

    /**
     * 回归树模型，实现机器学习
     *
     * @return 包含三个部分：
     *            200：状态码（代表成功）
     *            相关系数：（系数越接近1，代表模型回归效果越好）
     *            相关内容集合：影响参数名:影响程度 （越靠近1，代表影响结果的可能越大，过大可能过拟合）
     * @throws Exception m5P.setOptions以及buildClassifier异常，代表在实际机器学习中，可能存在异常
     */
    public ResponseResult<List<String>> regressionTree(Instances data) throws Exception {
        //Instances data = getDate();
        //Instances instances = preprocessData(data);
        /*
        使用回归树模型
         */
        M5P m5P = new M5P();
        m5P.setOptions(new String[]{""});
        m5P.buildClassifier(data);

        /*
         10-fold cross-validation
             k10折交叉验证，是一种常见的机器学习结果验证方式，
             本次验证我们主要关注相关系数（eval.correlationCoefficient）
         */
        Evaluation eval = new Evaluation(data);
        eval.crossValidateModel(m5P, data, 10, new Random(1), new String[] {});
        System.out.println(eval.correlationCoefficient());
        /// 注释部分为：平均绝对误差
        //System.out.println(eval.meanAbsoluteError());
        /// 注释部分为：验证结果的toString展示
        //System.out.println(eval.toSummaryString());

        /*
        结果集处理
         */
        String nodeToString = m5P.getM5RootNode().nodeToString();
        String[] split = nodeToString.split(":");
        String str="";
        for (int i = 0; i < split.length; i++) {
            str = str+split[i].trim().replaceAll("\\s*", "").trim();
        }
        str = str.replaceAll("Splitatt", "|");
        str =str.replaceAll("Splitval",":");
        str =str.replaceAll("LM", "|");
        String[] splitStr =  str.split("\\|");

        // 使用（Parallel Stream）并行管道流，快速得到数据结果
        List<String> collect = Stream.of(splitStr)
                .parallel() // 并行
                .filter(e -> e.contains(":")) // 过滤
                .distinct() // 去重
                .sorted( // 自定义排序
                        Comparator.comparing(e -> {
                            String a = (String)e;
                            double i = Double.parseDouble(a.split("\\:")[1]);
                            return i;
                        }).reversed() // 倒序
                )
                .collect(Collectors.toList()); // 生成集合

        ResponseResult<List<String>> regressionTreeResponseResult = new ResponseResult<>(200, String.valueOf(eval.correlationCoefficient()), collect);
        return regressionTreeResponseResult;
    }


}
